#pragma once

/*
 * redis client.
 */
#include "redis_connection_pool.h"
#include <string>
#include <memory>
#include <utility>
#include <vector>
#include <map>

namespace redisSpace {
    // define pool size.
    static const int gMaxPoolSize = 8;

    // define redisOPResult(redis op result).
    struct RedisValue;
    using redisOPResult = std::pair<bool, RedisValue>;
    using uint64_t = long long;

    // release op struct.
    struct releaseOP {
        releaseOP(std::unique_ptr<RedisConnectionPool>& pool,
            std::shared_ptr<RedisConnection>& conn) : conn_(conn), pool_(pool) {
        }
        ~releaseOP() {
            pool_->release(conn_);
        }
        std::shared_ptr<RedisConnection>& conn_;
        std::unique_ptr<RedisConnectionPool>& pool_;
    };

    class RedisClient {
    public:
        // default constructor, can Init(...) to initialize.
        RedisClient();

        // Constructor: timeout unit is second, and 0 is NO timeout limited.
        // poolSize is pool size for redis connections.
        RedisClient(const std::string& host, int port, std::string password = "", int index = 0, int timeout = 300, int poolSize = gMaxPoolSize);
        virtual ~RedisClient();

    public:
        bool Init(const std::string& host, int port, std::string password, int index = 0, int timeout = 0, int poolSize = gMaxPoolSize);
        
        // Key commands
        bool Select(int index);
        bool Exists(const std::string& key);
        bool Expire(const std::string& key, int seconds);
        bool PExpire(const std::string& key, int64_t milliseconds);
        int64_t TTL(const std::string& key);
        int64_t PTTL(const std::string& key);
        bool Persist(const std::string& key);
        redisOPResult Keys(const std::string& pattern);
        bool Rename(const std::string& key, const std::string& new_key);
        std::string Type(const std::string& key);

        // String commands
        redisOPResult Get(const std::string& key);
        
        template <typename T>
        bool Set(const std::string& key, const T& value, int timeout = 0) {
            auto conn = pool_->acquire();
            releaseOP releaser(pool_, conn);
            return conn->set(key, value, timeout).first;
        }
        
        bool SetNX(const std::string& key, const std::string& value);
        bool SetEX(const std::string& key, int seconds, const std::string& value);
        bool PSetEX(const std::string& key, int64_t milliseconds, const std::string& value);
        bool MSet(const std::map<std::string, std::string>& kv_pairs);
        redisOPResult MGet(const std::vector<std::string>& keys);
        int64_t Incr(const std::string& key);
        int64_t Decr(const std::string& key);
        int64_t IncrBy(const std::string& key, int64_t increment);
        int64_t DecrBy(const std::string& key, int64_t decrement);
        double IncrByFloat(const std::string& key, double increment);
        int Append(const std::string& key, const std::string& value);
        int StrLen(const std::string& key);

        // Hash commands
        template <typename T>
        bool HSet(const std::string& key, const std::string& field, const T& value) {
            auto conn = pool_->acquire();
            releaseOP releaser(pool_, conn);
            return conn->hset(key, field, value).first;
        }
        
        redisOPResult HGet(const std::string& key, const std::string& field);
        bool HDel(const std::string& key, const std::string& field);
        redisOPResult HGetAll(const std::string& key);
        bool HMSet(const std::string& key, const std::map<std::string, std::string>& field_values);
        redisOPResult HMGet(const std::string& key, const std::vector<std::string>& fields);
        int HLen(const std::string& key);
        redisOPResult HKeys(const std::string& key);
        redisOPResult HVals(const std::string& key);
        bool HExists(const std::string& key, const std::string& field);
        int64_t HIncrBy(const std::string& key, const std::string& field, int64_t increment);
        double HIncrByFloat(const std::string& key, const std::string& field, double increment);
        bool HSetNX(const std::string& key, const std::string& field, const std::string& value);

        // Set commands
        bool SAdd(const std::string& key, std::vector<std::string>& members);
        redisOPResult SMembers(const std::string& key);
        bool SRem(const std::string& key, std::vector<std::string>& members);
        int SCard(const std::string& key);
        bool SIsMember(const std::string& key, const std::string& member);
        redisOPResult SInter(const std::vector<std::string>& keys);
        redisOPResult SUnion(const std::vector<std::string>& keys);
        redisOPResult SDiff(const std::vector<std::string>& keys);
        redisOPResult SPop(const std::string& key, int count = 1);
        redisOPResult SRandMember(const std::string& key, int count = 1);

        // List commands
        bool LPush(const std::string& key, const std::vector<std::string>& members);
        redisOPResult LPop(const std::string& key);
        redisOPResult LRange(const std::string& key, int start, int end);
        bool RPush(const std::string& key, std::vector<std::string>& members);
        redisOPResult RPop(const std::string& key);
        int LLen(const std::string& key);
        redisOPResult LIndex(const std::string& key, int index);
        bool LSet(const std::string& key, int index, const std::string& value);
        int LRem(const std::string& key, int count, const std::string& value);
        bool LTrim(const std::string& key, int start, int end);
        redisOPResult RPopLPush(const std::string& source, const std::string& destination);

        // Sorted Set commands
        bool ZAdd(const std::string& key, const std::map<std::string, double>& member_scores);
        redisOPResult ZRange(const std::string& key, int start, int stop, bool with_scores = false);
        redisOPResult ZRevRange(const std::string& key, int start, int stop, bool with_scores = false);
        int ZCard(const std::string& key);
        double ZScore(const std::string& key, const std::string& member);
        bool ZRem(const std::string& key, const std::vector<std::string>& members);

        // Transaction commands
        bool Multi();
        redisOPResult Exec();
        bool Discard();
        bool Watch(const std::vector<std::string>& keys);
        bool Unwatch();

        // Server commands
        std::string Ping();
        redisOPResult Info(const std::string& section = "");
        bool FlushDB();
        bool FlushAll();

        // Pub/Sub commands
        int Publish(const std::string& channel, const std::string& message);
        bool Subscribe(const std::vector<std::string>& channels);
        bool Unsubscribe(const std::vector<std::string>& channels);

        // Scripting commands
        redisOPResult Eval(const std::string& script, 
                          const std::vector<std::string>& keys,
                          const std::vector<std::string>& args);
        redisOPResult EvalSHA(const std::string& sha1,
                             const std::vector<std::string>& keys,
                             const std::vector<std::string>& args);
        std::string ScriptLoad(const std::string& script);
        redisOPResult ScriptExists(const std::vector<std::string>& sha1s);
        bool ScriptFlush();

    private:
        std::unique_ptr<RedisConnectionPool> pool_;
    };
}